// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef DARWINN_DRIVER_USB_USB_IO_REQUEST_H_
#define DARWINN_DRIVER_USB_USB_IO_REQUEST_H_

#include <vector>

#include "driver/device_buffer.h"
#include "driver/dma_chunker.h"
#include "driver/dma_info.h"
#include "driver/usb/usb_ml_commands.h"

namespace platforms {
namespace darwinn {
namespace driver {

// Entry for either a hint, generated by code generator at compile time, or a
// request, submitted by device at run time.
class UsbIoRequest {
 public:
  // Basic type of the hint/request.
  enum class Type {
    // This is a bulk out request. Data is sent from host to device.
    kBulkOut = 0,

    // This is a bulk in request. Data is sent from device to host.
    kBulkIn,

    // This is an interrupt event. Signal is sent from device to host.
    kScHostInterrupt,
  };

  enum class SourceAndMatchStatus {
    // This is a hint, and we haven't seen a matching request from device.
    kHintNotYetMatched = 0,

    // This is a hint, and we have received a matching request from device.
    kHintAlreadyMatched,

    // This is a request submitted by device.
    kSubmittedByDevice,
  };

  // Constructor for device descriptors.
  UsbIoRequest(int id, UsbMlCommands::DescriptorTag tag);
  UsbIoRequest(int id, Type type, UsbMlCommands::DescriptorTag tag,
                  const DeviceBuffer& buffer);

  // Constructor for DMA hints.
  UsbIoRequest(DmaInfo* dma_info);

  // Accessors.
  Type GetType() const { return type_; }
  UsbMlCommands::DescriptorTag GetTag() const { return tag_; }
  SourceAndMatchStatus GetSourceAndMatchStatus() const {
    return source_and_match_status_;
  }
  const DeviceBuffer& GetBuffer() const { return chunker_.buffer(); }

  // Returns true if in given state.
  bool IsHeaderSent() const { return !header_.empty(); }
  bool IsActive() const { return chunker_.IsActive(); }
  bool IsCompleted() const {
    if (!chunker_.IsCompleted()) {
      return false;
    }
    // Interrupt will be always come through descriptor path.
    // It is completed when it was matched with hint, or submitted by device.
    if (type_ == Type::kScHostInterrupt) {
      return source_and_match_status_ !=
             SourceAndMatchStatus::kHintNotYetMatched;
    }
    return true;
  }

  // Returns true if there is chunk to transfer.
  bool HasNextChunk() const { return chunker_.HasNextChunk(); }

  // Returns not yet transferred chunk.
  DeviceBuffer GetNextChunk() { return chunker_.GetNextChunk(); }

  // Returns a next chunk upto "num_bytes".
  DeviceBuffer GetNextChunk(int num_bytes) {
    return chunker_.GetNextChunk(num_bytes);
  }

  // Notifies that "num_bytes" of transfer is completed.
  void NotifyTransferComplete(int num_bytes) {
    chunker_.NotifyTransfer(num_bytes);
  }

  // Returns number of active chunks assuming each chunk is "bytes".
  int GetActiveCounts(int bytes) const {
    return chunker_.GetActiveCounts(bytes);
  }

  // Marks this hint as it has been matched with a request/event sent from
  // device.
  void SetMatched();

  // Returns header.
  const std::vector<uint8_t>& header() const { return header_; }

  // Sets header.
  void SetHeader(const std::vector<uint8_t>& header) { header_ = header; }
  void SetHeader(std::vector<uint8_t>&& header) { header_ = std::move(header); }

  // Returns true if created from DMA hint.
  bool FromDmaHint() const { return dma_info_ != nullptr; }
  DmaInfo* dma_info() const { return dma_info_; }

  // Returns id.
  int id() const { return id_; }

 private:
  // ID for debugging purpose.
  const int id_;

  // Is this a hint or a request. If it's a hint, has it been matched with
  // soemthing sent from device?
  SourceAndMatchStatus source_and_match_status_;

  // Basic type of the hint/request.
  const Type type_;

  // Tag is more detailed information under Type. For example, a bulk out type
  // could be instruction, parameters, or input activations.
  const UsbMlCommands::DescriptorTag tag_;

  // DMA chunker.
  DmaChunker chunker_;

  // Contains valid pointer to DMA info if this is for hint.
  DmaInfo* dma_info_{nullptr};

  // Stores header used in single endpoint mode.
  std::vector<uint8> header_;
};

}  // namespace driver
}  // namespace darwinn
}  // namespace platforms

#endif  // DARWINN_DRIVER_USB_USB_IO_REQUEST_H_
